//
//  SketchProtocolTransport.java
//  SketchServer
//
//  Created by Brian Hinch on 9/9/08.
//  Copyright 2008 Tellart. All rights reserved.
//

import java.io.*;
import java.lang.*;
import java.math.*;

public class SketchProtocolTransport {
    
    protected static final boolean TRACE_DATA_IN = false; //set to true to output info about data from the input stream
    protected static final boolean TRACE_DATA_OUT = false; //set to true to output info about data going to the output stream
    
	protected InputStream in = null;
	protected OutputStream out = null; //BufferedWriter out = null;
    
    //these variables are used to handle INCOMING messages
    protected int incomingMessageBufferCursor;
    protected int incomingMessageBufferExpectedLength;
    protected byte[] inData;
    protected byte[] incomingMessageBuffer;
    
    protected boolean waitingForSetMessageFromInputBuffer = false; //this is set to true after a GET message is sent to the output stream
    protected long lastGetMessageSentAt = 0;
    protected long setMessageWaitTimeoutAt = 0;
    protected long longestGetSetRoundTripTime = 0;
    protected long shortestGetSetRoundTripTime = 1000000000;
    
    //the object to report new incoming messages to
    protected int listenerSource;
    protected SketchProtocolListener listener;
    
    //these variables are used to handle OUTGOING messages
    
    public SketchProtocolTransport(int code, SketchProtocolListener lsnr) {
        listenerSource = code;
        listener = lsnr;
    } //constructor
    
    /****************************************************************/

    protected void setIOStreams(InputStream i, OutputStream o) {
        in = i;
        out = o;
        System.out.println("SketchProtocolTransport.setIOStreams: in set to " + in + ", out set to " + out);
        incomingMessageBufferCursor = 0;
        incomingMessageBufferExpectedLength = -1;
        inData = new byte[SketchProtocol.SET_MESSAGE_LENGTH * 2];
        incomingMessageBuffer = new byte[SketchProtocol.SET_MESSAGE_LENGTH];
        notWaitingForSetMessage();
    } //setIOStreams

    protected int readInputBuffer() {
        int bytesRead = -1;
        try {
            bytesRead = in.read(inData, 0, inData.length);
            
            if (TRACE_DATA_IN) System.out.println("SketchProtocolTransport.readInputBuffer: READ IN " + bytesRead + " bytes: ");
            
            //look at each incoming byte
            for (int i = 0; i  < bytesRead; i++) {
                if (TRACE_DATA_IN) System.out.println("IN "+i+": " + Integer.toBinaryString(inData[i]));
                
                //if found start of a new message, move message buffer back to start
                if (inData[i] == SketchProtocol.SET_MESSAGE_OPCODE) {
                    if (TRACE_DATA_IN) System.out.println("SketchProtocolTransport.readInputBuffer: Found start of new SET message");
                    long roundTripTime = System.currentTimeMillis() - lastGetMessageSentAt;
                    if (roundTripTime > longestGetSetRoundTripTime) longestGetSetRoundTripTime = roundTripTime;
                    if (roundTripTime < shortestGetSetRoundTripTime) shortestGetSetRoundTripTime = roundTripTime;
                    if (TRACE_DATA_IN) System.out.println("SketchProtocolTransport.readInputBuffer: current GET to SET round trip time: " + (System.currentTimeMillis() - lastGetMessageSentAt) + "ms  shortest=" + shortestGetSetRoundTripTime + "ms longest=" + longestGetSetRoundTripTime + "ms");
                    incomingMessageBufferCursor = 0;
                    incomingMessageBufferExpectedLength = SketchProtocol.SET_MESSAGE_LENGTH;
                } else if (inData[i] == SketchProtocol.GET_MESSAGE_OPCODE) {
                    if (TRACE_DATA_IN) System.out.println("SketchProtocolTransport.readInputBuffer: Found start of new GET message");
                    incomingMessageBufferCursor = 0;
                    incomingMessageBufferExpectedLength = SketchProtocol.GET_MESSAGE_LENGTH;
				}
                    
                //if there is still space left in the incoming set message buffer, add this byte to it and advance the cursor
                if (incomingMessageBufferCursor < incomingMessageBufferExpectedLength) {
                    incomingMessageBuffer[incomingMessageBufferCursor] = inData[i];
                    incomingMessageBufferCursor++;
                }
                
                //when the incoming set message buffer fills...
                if (incomingMessageBufferCursor == incomingMessageBufferExpectedLength) {
                    if (TRACE_DATA_IN) System.out.println("SketchProtocolTransport.readInputBuffer: buffer has reached expected length! " +  incomingMessageBufferExpectedLength);
                    //...process it
                    if (incomingMessageBuffer[SketchProtocol.MESSAGE_OFFSET_OPCODE] == SketchProtocol.SET_MESSAGE_OPCODE) {
                        notWaitingForSetMessage();
                    }
                    listener.onData(listenerSource, incomingMessageBuffer, 0, incomingMessageBufferExpectedLength);
                    
                    //...and reset the buffer
                    incomingMessageBufferCursor = 0;
                    incomingMessageBufferExpectedLength = -1;
                    
                } else {
                    if (TRACE_DATA_IN) System.out.println("SketchProtocolTransport.readInputBuffer: buffer has NOT reached expected length yet... " + incomingMessageBufferCursor + " of " + incomingMessageBufferExpectedLength);
                }
                
            } //for i
            
            if (TRACE_DATA_IN) System.out.println("");
            
        } catch (IOException ioe) { }
        return bytesRead;
        
    } //readInputBuffer
    
    public boolean sendMessage(byte[] messageBuffer, int messageOffset, int messageLength, int multiSendCount, int multiSendDelay) throws IOException {
        if (TRACE_DATA_OUT) System.out.println("SketchProtocolTransport.sendMessage messageOffset=" + messageOffset + " messageLength=" + messageLength + " buffer[messageOffset]=" + messageBuffer[messageOffset] + " ####################################################");
        boolean ret = false; //return value
        
        if (isReadyToSendMessages()) {
            
            //<test>
            if (TRACE_DATA_OUT) {
                System.out.println("SketchProtocolTransport.sendMessage: WILL WRITE OUT " + messageLength + " bytes: ");
                for (int i = messageOffset; i < (messageOffset + messageLength); i++) {
                    System.out.println("OUT " + i + ": " + Integer.toBinaryString(messageBuffer[i]));
                }
            }
            //</test>
            
            try {
                if (messageBuffer[messageOffset + SketchProtocol.MESSAGE_OFFSET_OPCODE] == SketchProtocol.GET_MESSAGE_OPCODE) lastGetMessageSentAt = System.currentTimeMillis();
                out.write(messageBuffer, messageOffset, messageLength);
                ret = true; //set the return value
            } catch (IOException e) {
				if (!e.getMessage().equals("Broken pipe")) {
					System.err.println("OutputStream write error: " + e.getMessage());
					e.printStackTrace(System.err);
				}
            }
        
            //if the message just sent was a SET then turn on the flag that causes us to wait for a corresponding GET
            if (messageBuffer[messageOffset + SketchProtocol.MESSAGE_OFFSET_OPCODE] == SketchProtocol.GET_MESSAGE_OPCODE) {
                startWaitingForSetMessage();
            }
        } else {
            if (TRACE_DATA_OUT) System.out.println("SketchProtocolTransport.sendMessage: NOT SENDING BECAUSE isReadyToSendMessages()=" + isReadyToSendMessages());
        }
        
        return ret;
        
    } //sendMessage
    
    private void startWaitingForSetMessage() {
        waitingForSetMessageFromInputBuffer = false;
        setMessageWaitTimeoutAt = lastGetMessageSentAt + SketchProtocol.GET_RESPONSE_TIMEOUT_MS;
    }
    
    private void notWaitingForSetMessage() {
        waitingForSetMessageFromInputBuffer = false;
        setMessageWaitTimeoutAt = 0;
    }
    
    /*
     * returns true if the system is ready to send messages, or false if sending a message would fail
     *
     */
    public boolean isReadyToSendMessages() {
        return (out != null) && (!waitingForSetMessageFromInputBuffer || (System.currentTimeMillis() >= setMessageWaitTimeoutAt));
    } //isReadyToSendMessages


}
